import socket
import time
import threading
print('Starting server...')
#服务器套接字的端口号
serverPort = 12000 
def handle_socket(cns, addr, addrstr):
    sendmessage = str(addr[0]) + '已上线！'
    print(sendmessage)
    lock.acquire()
    for cli_addrstr in cns_dir:
        cns_dir[cli_addrstr].send(sendmessage.encode())
    lock.release()
    while True:
        #阻塞响应接受的信息
        try:
            data = cns.recv(1024) 
        except:
            break
        data = data.decode()
        if data != '\0': #判断接收到的信息是在线响应信息还是用户输入信息
            sendmessage = '[' + str(addr[0]) + ']' + ':' + data
            print('receive from' + sendmessage)
            #time.sleep(1)
            lock.acquire()
            for cli_addrstr in cns_dir:
                cns_dir[cli_addrstr].send(sendmessage.encode())
            lock.release()

def monitor_socket():
    while True:
        #将所有用户的在线信息置位0；0代表离线，1代表在线
        offline_list = []
        lock.acquire()
        for cli_addrstr in addr_dir:
            addr_dir[cli_addrstr] = 0
        #向客户套接字发信息判断其是否在线
        for cli_addrstr in cns_dir:
            try:
                cns_dir[cli_addrstr].send('\0'.encode())
            except:
                offline_list.append(cli_addrstr) #记录离线的设备
        lock.release()
        time.sleep(1)
        lock.acquire()
        #offline_list = [addrstr for addrstr in addr_dir if addr_dir[addrstr] == 0] 
        #print('offline_list:',offline_list)
        #从字典中释放已经离线的用户
        for addrstr in offline_list:
            offline =  addrstr[1:len(addrstr) - int(addrstr[0])] + '下线了!'
            print(offline)
            addr_dir.pop(addrstr)
            cns_dir.pop(addrstr)
            #通知其他用户该用户已下线
            for cli_addrstr in cns_dir:
                try:
                    cns_dir[cli_addrstr].send(offline.encode())
                except:
                   continue
        lock.release()        
        
if __name__ == '__main__':
    #创建服务器欢迎套接字
    ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    #将端口号12000与该服务器的套接字绑定在一起,即显式地为该套接字分配一个端口号
    ss.bind(('', serverPort))
    #在创建这扇欢迎之门后，我们将等待并聆听某个客户敲门：参数定义了请求连接的最大数
    ss.listen()
    print('The server is ready to connect!')
    cns_dir = {}    #用来记录套接字
    addr_dir = {}  #用来记录在线的设备
    lock = threading.Lock()
    #建立线程，监视用户是否在线
    monitor_thread = threading.Thread(target = monitor_socket)
    monitor_thread.start()
    #while循环将允许UDPServer无限期地接收并处理来自客户的分组
    while True:
        #在服务器中创建了一个称为connectionSocket的新套接字，由这个特定的客户专用
        cns, addr = ss.accept() 
        #用ip与端口号表示客户地址
        addrstr = str(len(str(addr[1]))) + str(addr[0]) + str(addr[1])
        #用户在线，将用户信息添加到字典中
        addr_dir[addrstr] = 1 
        cns_dir[addrstr] = cns
        print('一个客户端已连接！')
        #建立子线程，监听从当前用户得到的信息
        client_thread = threading.Thread(target = handle_socket, args=[cns, addr, addrstr])
        client_thread.start()